package net.jdm.constraint.dependence;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import net.jdm.constraint.dependence.DependenceConstraint.DependenceConstraintType;
import net.jdm.domain.dependence.ContainerDependence;

public class ViolationDependenciesFinder<T extends ContainerDependence> {
	public Set<? extends T> findViolationDependences(Set<? extends T> actualDependences,
			Set<DependenceDefine> definedDependences,DependenceConstraintType dependenceConstraintType){
		switch(dependenceConstraintType){
		   case DENY:return findViolationDenyDependences(actualDependences,definedDependences);
		   case FROM_TO:return findViolationFromToDependences(actualDependences,definedDependences);
		   case ONLY_FROM:return findViolationOnlyFromDependences(actualDependences,definedDependences);
		   case ONLY_TO:return findViolationOnlyToDependences(actualDependences,definedDependences);
		   default:return findViolationDenyDependences(actualDependences,definedDependences);
		}
	}

	private Set<? extends T> findViolationDenyDependences(
			Set<? extends T> actualDependences,Set<DependenceDefine> definedDependences) {
		Set<T> result=new HashSet<T>();
		result.addAll(actualDependences);
		return getSames(result,definedDependences);
	}

	private Set<? extends T> findViolationFromToDependences(
		Set<? extends T> actualDependences,Set<DependenceDefine> definedDependences) {
		Set<T> result=new HashSet<T>();
		result.addAll(actualDependences);
		Set<T> unconcernedDependences=new HashSet<T>();
		for(T actualDependence:actualDependences){
			boolean fromIsUnconcerned=true;
			boolean toIsUnconcerned=true;
			for(DependenceDefine definedDependence:definedDependences){
				if(actualDependence.getFrom().getName().equals(definedDependence.to)&&actualDependence.getTo().getName().equals(definedDependence.from)){
					fromIsUnconcerned=false;
					toIsUnconcerned=false;
					break;
				}
               if(actualDependence.getFrom().getName().equals(definedDependence.from)){
            	   fromIsUnconcerned=false;
            	   break;
               }
               if(actualDependence.getTo().getName().equals(definedDependence.to)){
            	   toIsUnconcerned=false;
            	   break;
               }
			}
			if(fromIsUnconcerned&&toIsUnconcerned)
				unconcernedDependences.add(actualDependence);
		}
		result.removeAll(unconcernedDependences);
		Set<T> defineded = getSames(result, definedDependences);
		result.removeAll(defineded);
		return result;
	}


	private Set<? extends T> findViolationOnlyFromDependences(
			Set<? extends T> actualDependences,Set<DependenceDefine> definedDependences) {
		return findViolationUnidirectionDependences(actualDependences,
				definedDependences, true);
	}
	private Set<? extends T> findViolationOnlyToDependences(
			Set<? extends T> actualDependences,Set<DependenceDefine> definedDependences) {
		return findViolationUnidirectionDependences(actualDependences,
				definedDependences, false);
	}
	private Set<? extends T> findViolationUnidirectionDependences(
			Set<? extends T> actualDependences,
			Set<DependenceDefine> definedDependences, boolean direction) {
		Set<T> found = new HashSet<T>();
		Map<String,Set<T>> followConstraintDependenceMap=new HashMap<String,Set<T>>();
		Map<String,Set<T>> concernedDependencesMap=new HashMap<String,Set<T>>();
		for (T actualDependence : actualDependences) {
			Set<T> followConstraintDependences=followConstraintDependenceMap.get(getDependNode(actualDependence, direction));
			if(followConstraintDependences==null){
				followConstraintDependences=new HashSet<T>();//NOPMD
				followConstraintDependenceMap.put(getDependNode(actualDependence, direction),followConstraintDependences);
			}
			Set<T> concernedDependences=concernedDependencesMap.get(getDependNode(actualDependence, direction));
			if(concernedDependences==null){
				concernedDependences=new HashSet<T>();//NOPMD
				concernedDependencesMap.put(getDependNode(actualDependence, direction),concernedDependences);
			}
			for (DependenceDefine defindedDependence : definedDependences) {
				if(getDependNode(actualDependence, direction).equals(getDependNode(defindedDependence, direction))){
		            if(getDependNode(actualDependence,!direction).equals(getDependNode(actualDependence, direction))){
		            	continue;
					}else{
						concernedDependences.add(actualDependence);
						if(getDependNode(actualDependence,!direction).equals(getDependNode(defindedDependence,!direction))){
							followConstraintDependences.add(actualDependence);
						}
					}
				}
		   }
		}
		for(Entry<String, Set<T>> concernedDependencesEntrySet:concernedDependencesMap.entrySet()){
			Set<T> concernedDependences=concernedDependencesEntrySet.getValue();
			Set<T> followConstraintDependence=followConstraintDependenceMap.get(concernedDependencesEntrySet.getKey());
			concernedDependences.removeAll(followConstraintDependence);
			found.addAll(concernedDependences);
		}
		return found;
	}

	private String getDependNode(T actualDependence, boolean direction) {
		if(direction)
		   return actualDependence.getTo().getName();
		else

			return actualDependence.getFrom().getName();
	}
	private String getDependNode(DependenceDefine dependenceConfig, boolean direction) {
		if(direction)
		   return dependenceConfig.to;
		else

			return dependenceConfig.from;
	}

	private Set<T> getSames(Set<T> actualDependences,
			Set<DependenceDefine> definedDependences) {
		Set<T> result=new HashSet<T>();
		for(T actualDependence:actualDependences){
			for(DependenceDefine dependenceConfig:definedDependences)
			if(actualDependence.getFrom().getName().equals(dependenceConfig.from)&&actualDependence.getTo().getName().equals(dependenceConfig.to)){
				result.add(actualDependence);
			}
		}
		return result;
	}
}